package com.blog.service.Impl;

import com.alibaba.fastjson.JSONObject;
import com.blog.Utils.OkHttpUtil;
import com.blog.base.Constans;
import com.blog.base.MyGPTUrl;
import com.blog.base.ThreadLocalUserId;
import com.blog.bean.domain.ChatGpt;
import com.blog.bean.domain.ChatGptDirectory;
import com.blog.bean.domain.GptMessage;
import com.blog.bean.domain.GptToken;
import com.blog.bean.dto.GptDTO;
import com.blog.bean.vo.GptVO;
import com.blog.exception.ServiceException;
import com.blog.service.ChatGptDirectoryService;
import com.blog.service.ChatGptService;
import com.blog.service.GptService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author 任浩
 * @version 1.0
 */
@Slf4j
@Service
public class GptServiceImpl implements GptService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private ChatGptService chatGptService;

    @Autowired
    private ChatGptDirectoryService chatGptDirectoryService;

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public GptVO getChatGpt(GptDTO chatData) {
        StringBuilder sb = new StringBuilder(MyGPTUrl.GPT_CALL_URL);
        Map<String,Object> params = new HashMap<>();
        Map<String,String> headers = new HashMap<>();
        List<GptMessage> gptMessages = chatData.getGptData();
        //注意，当前提问带过来的每次只有一条数据。

        //一进来，插入当前提问的数据。
        chatData.setCategory(saveGptData(chatData.getCategory(),gptMessages));

        headers.put("Content-Type","application/json");
        //得到访问的access_token
        GptToken gptToken = getGPTToken();
        if(gptToken == null || gptToken.getAccessToken() == null || "".equals(gptToken.getAccessToken())){
            log.info("获取到的gtp_token不能为null，或者不能为空字符串");
            throw new ServiceException("获取到的gtp_token不能为null，或者不能为空字符串");
        }
        sb.append("?access_token=" + gptToken.getAccessToken());
        /**
         * 封装请求体参数，请求体参数需要我们放在请求体里面，并且我们需要设置message参数，message参数是一个列表，列表里面包含我们要发送的问题。
         * 单轮查询的话，我们只需要发送长度为1的列表。
         * 多轮查询的话，我们需要发送长度不为1的列表。
         *
         * 示例
         *  1个成员示例，"messages": [ {"role": "user","content": "你好"}]
         * ·3个成员示例，"messages": [ {"role": "user","content": "你好"},{"role":"assistant","content":"需要什么帮助"},{"role":"user","content":"自我介绍下"}]
         * （2）最后一个message为当前请求的信息，前面的message为历史对话信息
         * （3）成员数目必须为奇数，成员中message的role值说明如下：奇数位message的role值必须为user，偶数位message的role值为assistan
         */
        //该参数是请求体里面必填的参数。
        params.put("messages",gptMessages);
        //todo 此参数可以根据业务来定，这里我们设置为true，表示我们请求的是流式数据。
        if(chatData.getStream()){
            params.put("stream",true);
        }

        String dataJson = JSONObject.toJSONString(params);
        String res = OkHttpUtil.post(sb.toString(),dataJson,headers);
        JSONObject jsonObject = JSONObject.parseObject(res);
        String content = jsonObject.getString("result");
        log.info("返回的结果为: {}",content);

        //todo 根据结果，把结果存储到表里面。
        List<GptMessage> chatMessageList = new ArrayList<>();
        //然后把gpt回答出来的问题记录下来。
        GptMessage answer = new GptMessage("assistant", content);
        chatMessageList.add(answer);
        chatData.setCategory(saveGptData(chatData.getCategory(),chatMessageList));

        //todo 根据响应头，得到这一分钟内还有多少请求次数，做一个限流约束。

        return new GptVO(chatData.getCategory(), answer);
    }


    /**
     * 获取gpt的access_token接口
     * @return
     */
    @Override
    public GptToken getGPTToken() {
        //先到redis里面判断GPT_token是否还有，如果没有的话，重新生成得到token，有的话复用。
        String gptToken = redisTemplate.opsForValue().get(Constans.GPT_TOKEN_REDIS_NAME);

        if(gptToken != null && !"".equals(gptToken)){
            log.info("当前gpt_token没有过期");
            return new GptToken(gptToken,null,null,null);
        }
        log.info("当前gpt_token已经过期，重新生成gpt_token");

        Map<String,String> headers = new HashMap<>();
        headers.put("Content-Type","application/json");
        StringBuilder sb = new StringBuilder(MyGPTUrl.GET_TOKEN_URL);
        sb.append("?grant_type=client_credentials");
        sb.append("&client_id=" + Constans.APP_KEY);
        sb.append("&client_secret=" + Constans.SCRET_KEY);

        String res = OkHttpUtil.post(sb.toString(),"",headers);

        GptToken data = JSONObject.parseObject(res, GptToken.class);
        redisTemplate.opsForValue().set(Constans.GPT_TOKEN_REDIS_NAME,data.getAccessToken(),data.getExpiresIn(),
                                                                                                TimeUnit.SECONDS);
        return data;
    }


    /**
     * 存储gpt返回数据
     * @param category
     * @param data
     */
    public Integer saveGptData(Integer category,List<GptMessage> data){
        try {
            /**
             * 往GPT聊天目录表插数据
             */
            Integer userId = Integer.parseInt(ThreadLocalUserId.get());
            //目录只保存信息的前10个字符。
            String content = data.get(data.size() - 1).getContent();
            content = content.length() > 10 ? content.substring(0,10) : content;

            if(category == null){ //说明这是一个新的目录。
                //创建一个新目录
                ChatGptDirectory chatGptDirectory = new ChatGptDirectory(null, userId, content, null, null);
                chatGptDirectoryService.insert(chatGptDirectory);
                category = chatGptDirectory.getId();
            }
            else{
                //目录存在，首先检查一下目录是否确实存在。
                ChatGptDirectory chatGptDirectory = chatGptDirectoryService.getById(category);
                if(chatGptDirectory == null){
                    throw new ServiceException("gpt要聊天的目录不存在");
                }
                //目录存在，那么就直接更新。
                chatGptDirectory.setTitle(content);
                chatGptDirectoryService.updateById(chatGptDirectory);
            }

            /**
             * 往GPT聊天记录表插数据
             */
            List<ChatGpt> chatGptList = new ArrayList<>();
            Integer categoryId = category;
            data.forEach(item ->{
                ChatGpt chatGpt = new ChatGpt(null, categoryId,item.getRole(),item.getContent(),null);
                chatGptList.add(chatGpt);
            });
            chatGptService.saveBatchs(chatGptList);
        }
        catch (Exception e){
            e.printStackTrace();
            throw new ServiceException("保存gpt数据失败, 错误原因: " +e.getMessage());
        }
        return category;
    }
}
